package repository

import (
	"context"
	"errors"
	"fmt"
	"time"
	"webook/internal/domain"
	"webook/internal/repository/cache"
	"webook/internal/repository/dao"
)

type ArticleRepository interface {
	Create(ctx context.Context, article domain.Article) (int64, error)
	Update(ctx context.Context, article domain.Article) (int64, error)
	Sync(ctx context.Context, article domain.Article) (int64, error)
	List(ctx context.Context, authorId int64, offset int, limit int) ([]domain.Article, error)
	GetById(ctx context.Context, authorId int64, articleId int64) (domain.Article, error)
	GetPubById(ctx context.Context, articleId int64) (domain.PublishArticle, error)
	ListPub(ctx context.Context, offset int, limit int) ([]domain.Article, error)
}

type CachedArticleRepository struct {
	dao      dao.ArticleDAO
	cache    cache.ArticleCache
	UserRepo UserRepository // 聚合UserRepo
}

func NewCachedArticleRepository(dao dao.ArticleDAO, cache cache.ArticleCache, userRepo UserRepository) ArticleRepository {
	return &CachedArticleRepository{
		dao:      dao,
		cache:    cache,
		UserRepo: userRepo,
	}
}

func (repo *CachedArticleRepository) ListPub(ctx context.Context, offset int, limit int) ([]domain.Article, error) {
	arts, err := repo.dao.ListPub(ctx, offset, limit)
	if err != nil {
		return []domain.Article{}, err
	}
	var retArts = make([]domain.Article, len(arts))
	for i, art := range arts {
		retArts[i] = domain.Article{
			Id:      art.Id,
			Title:   art.Title,
			Content: art.Content,
			Author: domain.Author{
				Id: art.AuthorId,
			},
			Ctime: art.Ctime,
			Utime: art.Utime,
		}
	}
	return retArts, nil
}

func (repo *CachedArticleRepository) GetPubById(ctx context.Context, articleId int64) (domain.PublishArticle, error) {
	// 如何设计缓存方案
	// 1. 先从缓存中找
	var key = fmt.Sprintf("pub_article:detail:%d", articleId)
	art, err := repo.cache.GetPub(ctx, key)
	if err == nil {
		// 缓存命中直接返回
		return art, nil
	}
	if err != cache.ErrArticleNotFound {
		// 还要不要继续查数据库
		return domain.PublishArticle{}, err
	}
	dbArt, err := repo.dao.GetPubById(ctx, articleId)
	if err != nil {
		return domain.PublishArticle{}, err
	}
	// 2. 找不到从数据库加载
	// 聚合查询用户
	author, err := repo.UserRepo.FindById(ctx, dbArt.AuthorId)
	if err != nil {
		// TODO 记录日志：
		return domain.PublishArticle{}, err
	}
	var retArt = domain.PublishArticle{
		Id:      dbArt.Id,
		Title:   dbArt.Title,
		Content: dbArt.Content,
		// author信息从哪来?
		Author: domain.Author{
			Id:   author.Id,
			Name: author.Nickname,
		},
		Ctime: dbArt.Ctime,
		Utime: dbArt.Utime,
	}
	// 3. 异步设置缓存
	go func() {
		// 这里可以忽略错误
		err = repo.cache.SetPub(ctx, key, retArt, time.Minute*30)
		if err != nil {
			// TODO: 记录日志缓存设置失败
		}
	}()
	return retArt, nil
}

func (repo *CachedArticleRepository) GetById(ctx context.Context, authorId int64, articleId int64) (domain.Article, error) {
	// 1. 先查询缓存
	var key = fmt.Sprintf("article:detail:%d", articleId)
	art, err := repo.cache.Get(ctx, key)
	if err == nil {
		// 缓存命中直接返回
		// 特殊处理authorId不相等的情况
		if art.Author.Id != articleId {
			// TODO 记录日志：authorId不一致
			return domain.Article{}, errors.New("author_id is invalid")
		}
		return art, nil
	}
	if err != cache.ErrArticleNotFound {
		// 还要不要查询数据库
		return domain.Article{}, err
	}
	// 2. 查询数据库
	dbArt, err := repo.dao.GetById(ctx, authorId, articleId)
	if err != nil {
		return domain.Article{}, err
	}
	return domain.Article{
		Id:      dbArt.Id,
		Title:   dbArt.Title,
		Content: dbArt.Content,
		Author: domain.Author{
			Id: dbArt.AuthorId,
		},
		Ctime: dbArt.Ctime,
		Utime: dbArt.Utime,
	}, nil
}
func (repo *CachedArticleRepository) List(ctx context.Context, authorId int64, offset int, limit int) ([]domain.Article, error) {
	// 在repository层面设计缓存方案
	// 1. 先从缓存中找
	var key = fmt.Sprintf("article:first_page:%d", authorId)
	if offset == 0 && limit <= 100 {
		res, err := repo.cache.GetFirstPage(ctx, key)
		if err == nil {
			// 直接返回
			// 这里需要注意limit可能超过总记录数
			return res[:min(limit, len(res))], nil
		}
		if err != cache.ErrArticleNotFound {
			// 这里还要不要查询数据库？？？
			return nil, err
		}
	}
	// 2. 查询数据库
	arts, err := repo.dao.FindByAuthorId(ctx, authorId, offset, limit)
	if err != nil {
		return nil, err
	}
	var articles = make([]domain.Article, len(arts))
	for i, article := range arts {
		articles[i] = domain.Article{
			Id:      article.Id,
			Title:   article.Title,
			Content: article.Content,
			Author: domain.Author{
				Id: article.Id,
			},
			Ctime: article.Ctime,
			Utime: article.Utime,
		}
	}
	// 3. 异步写缓存
	go func() {
		if offset == 0 && limit <= 100 {
			// 这里不用管错误
			// 缓存第一页数据
			err = repo.cache.SetFirstPage(ctx, key, articles, time.Minute*30)
			if err != nil {
				// TODO: 日志记录设置第一页缓存失败
			}
		}
	}()
	go func() {
		// 预缓存第一条数据
		// 重要点: 缓存过期时间很短
		// 优化点：不缓存大文件
		if len(articles) > 0 {
			var key = fmt.Sprintf("article:detail:%d", articles[0].Id)
			err = repo.cache.Set(ctx, key, articles[0], time.Second*30)
			if err != nil {
				// TODO: 日志记录设置预缓存缓存失败
			}
		}
	}()
	return articles, nil
}

// Sync 制作库与线上库数据同步
func (repo *CachedArticleRepository) Sync(ctx context.Context, article domain.Article) (int64, error) {
	var key = fmt.Sprintf("article:first_page:%d", article.Author.Id)
	defer func() {
		// 更新缓存（删除）
		err := repo.cache.DelFirstPage(ctx, key)
		if err != nil {
			// TODO: 记录日志删除缓存失败
		}
	}()
	// 1. 可以选择在repo层面用事务处理
	// 2. 继续下沉交给dao层
	return repo.dao.Sync(ctx, dao.Article{
		Id:       article.Id,
		Title:    article.Title,
		Content:  article.Content,
		AuthorId: article.Author.Id,
	})
}

func (repo *CachedArticleRepository) Create(ctx context.Context, article domain.Article) (int64, error) {
	var key = fmt.Sprintf("article:first_page:%d", article.Author.Id)
	defer func() {
		// 更新缓存（删除）
		err := repo.cache.DelFirstPage(ctx, key)
		if err != nil {
			// TODO: 记录日志删除缓存失败
		}
	}()
	id, err := repo.dao.Insert(ctx, dao.Article{
		Title:    article.Title,
		Content:  article.Content,
		AuthorId: article.Author.Id,
	})
	return id, err
}

func (repo *CachedArticleRepository) Update(ctx context.Context, article domain.Article) (int64, error) {
	var key = fmt.Sprintf("article:first_page:%d", article.Author.Id)
	defer func() {
		// 更新缓存（删除）
		err := repo.cache.DelFirstPage(ctx, key)
		if err != nil {
			// TODO: 记录日志删除缓存失败
		}
	}()
	id, err := repo.dao.Update(ctx, dao.Article{
		Id:       article.Id,
		Title:    article.Title,
		Content:  article.Content,
		AuthorId: article.Author.Id,
	})
	return id, err
}
